﻿#region using statements

using System.Collections.Generic;

using Microsoft.Xna.Framework;

using NGen.Managers.GameObject;
using NGen.Managers.Logging;

#endregion

namespace NGen.Managers.Collision
{

	#region using declarations

	#endregion

	#region boundingbox utility

	/// <summary>
	/// 	This is a static class to reduce the need for creating new bounding box objects to do predictive
	/// 	collision checks.
	/// </summary>
	public static class StaticBoundingBox2D
	{
		/// <summary>
		/// 	This is an object that will be used by this class for calculations
		/// </summary>
		private static BoundingBox s_boxToReturn = new BoundingBox(new Vector3(0, 0, 0), new Vector3(0, 0, 0));

		/// <summary>
		/// 	This method is used to calculate the position of a bounding box and then return it for use
		/// 	in the collision detection loop.  It makes calculations based on the object's current position
		/// </summary>
		/// <param name = "p_objIn">The object whose bounding box is needed; has to have inherited ICollidable</param>
		/// <returns>This method returns a bounding box that is updated to the game object's current next position</returns>
		public static BoundingBox GetNewBoundingBox(GameObject2D p_objIn) {
			s_boxToReturn.Min.X = p_objIn.m_position.X;
			s_boxToReturn.Min.Y = p_objIn.m_position.Y;

			s_boxToReturn.Max.X = s_boxToReturn.Min.X + ((ICollidable2D) p_objIn).BoundingBox.Max.X;
			s_boxToReturn.Max.Y = s_boxToReturn.Min.Y + ((ICollidable2D) p_objIn).BoundingBox.Max.Y;

			return s_boxToReturn;
		}
	}

	#endregion

	/// <summary>
	/// 	This is the collision manager object.  The internal workings of the game engine are the only things
	/// 	that should call on this object.
	/// </summary>
	public sealed class ManagerCollision2D : GameManager, IManagerCollision2D
	{
		#region local fields

		/// <summary>
		/// 	List of objects that are mobile
		/// </summary>
		private readonly HashSet<uint> m_hsDynamicObjects = new HashSet<uint>();

		/// <summary>
		/// 	List of objects that are stationary
		/// </summary>
		private readonly HashSet<uint> m_hsStaticObjects = new HashSet<uint>();

		#endregion

		#region IGameManager implemenetation

		/// <summary>
		/// 	_Initialize the manager
		/// </summary>
		internal override void Initialize() {
			m_hsDynamicObjects.Clear();
			m_hsStaticObjects.Clear();
			CurrentLoggingLevel = LoggingLevel.MAXIMUM;
			SkipDraw = true;
		}

		/// <summary>
		/// 	This method does all of the collision checks between all of the dynamic objects and each dynamic
		/// 	object with each static object.  It is doing n^2 checks.  It is currently using predictive AABB collision 
		/// 	detection.  When a collision is detected both collidable objects are notified of the collision and are
		/// 	passed the id of the offending object.
		/// </summary>
		/// <param name = "p_gameTime">the game time object</param>
		internal override void Update(GameTime p_gameTime) {
			var objectMgr = GameEngine.GetService<IManagerGameObjects>();

			var objItr1 = m_hsDynamicObjects.GetEnumerator();
			while(objItr1.MoveNext()) {
				var objItr2 = objItr1;
				//check all moving objects against all other moving objects
				while(objItr2.MoveNext()) {
					if(StaticBoundingBox2D.GetNewBoundingBox(objectMgr.GetGameObjectById<GameObject2D>(objItr1.Current)).Contains(StaticBoundingBox2D.GetNewBoundingBox(objectMgr.GetGameObjectById<GameObject2D>(objItr2.Current))).Equals(ContainmentType.Intersects)) //if the 2 objects bounding boxes intersect
					{
						//send collision messages to those 2 objects
						var msg1 = new CollisionMessage2D(objectMgr.GetGameObjectById<GameObject2D>(objItr1.Current));
						var msg2 = new CollisionMessage2D(objectMgr.GetGameObjectById<GameObject2D>(objItr2.Current));

						((ICollidable2D) objectMgr.GetGameObjectById(objItr1.Current)).Collided(msg2);
						((ICollidable2D) objectMgr.GetGameObjectById(objItr2.Current)).Collided(msg1);
					}
				}

				var objItr3 = m_hsStaticObjects.GetEnumerator();
				//check all moving objects against all static objects
				while(objItr3.MoveNext()) {
					if(StaticBoundingBox2D.GetNewBoundingBox(objectMgr.GetGameObjectById<GameObject2D>(objItr1.Current)).Contains(StaticBoundingBox2D.GetNewBoundingBox(objectMgr.GetGameObjectById<GameObject2D>(objItr3.Current))).Equals(ContainmentType.Intersects)) //if the 2 objects bounding boxes intersect
					{
						//send collision messages to those 2 objects
						var msg1 = new CollisionMessage2D(objectMgr.GetGameObjectById<GameObject2D>(objItr1.Current));
						var msg2 = new CollisionMessage2D(objectMgr.GetGameObjectById<GameObject2D>(objItr3.Current));

						((ICollidable2D) objectMgr.GetGameObjectById(objItr1.Current)).Collided(msg1);
						((ICollidable2D) objectMgr.GetGameObjectById(objItr3.Current)).Collided(msg2);
					}
				}
			}
		}

		/// <summary>
		/// 	Cleanup this manager
		/// </summary>
		internal override void Cleanup() {
			m_hsDynamicObjects.Clear();
			m_hsStaticObjects.Clear();
		}

		#endregion

		#region local methods

		/// <summary>
		/// 	This method is used to add a game object to the Collision Manager.  The identifying number
		/// 	is necessary as well as specifying the correct type of object.  The id is used to find the
		/// 	object to be tested on and the type is whether or not it needs to be checked for collisions
		/// 	due to its movement.
		/// </summary>
		/// <param name = "p_intCollidableId">The id of the object to be added</param>
		/// <param name = "p_blStatic">True if it is a static object and false if it is a dynamic object</param>
		/// <returns>true is returned if the object's id is added to the collision manager; 
		/// 	false otherwise</returns>
		public bool RegisterCollidable(uint p_intCollidableId, bool p_blStatic) {
			if(GameEngine.GetService<IManagerGameObjects>().GetGameObjectIdList().Contains(p_intCollidableId)) {
				if(p_blStatic && !m_hsStaticObjects.Contains(p_intCollidableId) &&
				   GameEngine.GetService<IManagerGameObjects>().GetGameObjectById(p_intCollidableId) is ICollidable2D) {
					m_hsStaticObjects.Add(p_intCollidableId);
					return true;
				}
				if(!p_blStatic && !m_hsDynamicObjects.Contains(p_intCollidableId) &&
				   GameEngine.GetService<IManagerGameObjects>().GetGameObjectById(p_intCollidableId) is ICollidable2D) {
					m_hsDynamicObjects.Add(p_intCollidableId);
					return true;
				}
			}
			return false;
		}

		/// <summary>
		/// 	This method is used to remove a game object from the Collision Manager.  This should be used
		/// 	for when an object no longer needs to have collisions checked on it.
		/// </summary>
		/// <param name = "p_intCollidableId">The id of the object to be removed</param>
		/// <returns>true is returned if the object's id is removed from the collision manager; 
		/// 	false otherwise</returns>
		public bool UnRegisterCollidable(uint p_intCollidableId) {
			if(m_hsStaticObjects.Contains(p_intCollidableId)) {
				m_hsStaticObjects.Remove(p_intCollidableId);
				return true;
			}
			if(m_hsDynamicObjects.Contains(p_intCollidableId)) {
				m_hsDynamicObjects.Remove(p_intCollidableId);
				return true;
			}
			return false;
		}

		#endregion
	}
}